﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EasyStep.TestExecution.Interfaces;
using EasyStep.TestExecution.Data;
using System.IO;
using EasyStep.Business.Interfaces;
using EasyStep.Business.Constants;
using EasyStep.Data.Entities;
using System.Drawing;

namespace EasyStep.TestExecution.Manager
{
    public class TestHistoryManager : ITestHistoryManager
    {
        private const string mainPrefix = "test.info.";
        private const string mainSuffix = ".test.cvs";
        private const string logPrefix = "test.result.";
        private const string logSuffix = ".log";

        public ITestDataContext DataContext { get; set; }
        protected string ResultsRootPath
        {
            get
            {
                return DataContext.PathReplacements[MainApplicationServiceConstants.FileSystemDataStoragePath];
            }
        }
        private int prefixLength;
        private int suffixLength;
        public TestHistoryManager(ITestDataContext dataContext)
        {
            prefixLength = mainPrefix.Length;
            suffixLength = mainSuffix.Length;
            DataContext = dataContext;
        }

        public IEnumerable<string> GetAllProjects()
        {
            return Directory.GetDirectories(GetPathName()).Select(d => Path.GetFileName(d)).OrderBy(d => d);
        }

        public IEnumerable<string> GetProjectTests(string projectName)
        {
            return Directory.GetDirectories(GetPathName(projectName)).Select(d => Path.GetFileName(d)).OrderBy(d => d);
        }
        public IEnumerable<string> GetTestResults(string projectName, string testName)
        {
            return Directory.GetDirectories(GetPathName(projectName, testName)).Select(d => Path.GetFileName(d)).OrderByDescending(d => d);
        }

        public IEnumerable<KeyValuePair<string, IEnumerable<TestStepResult>>> GetTestResultBrowsers(string projectName, string testName, string resultName,bool quick =true)
        {
            return Directory.EnumerateFiles(GetPathName(projectName, testName, resultName), "*" + mainSuffix)
                .Select(d =>
                {
                    var bname = Path.GetFileName(d).Remove(0, prefixLength).RemoveLast(suffixLength);
                    if (quick)
                        return new KeyValuePair<string, IEnumerable<TestStepResult>>(bname, GetTestResultSteps(d, quick));
                    else
                        return new KeyValuePair<string, IEnumerable<TestStepResult>>(bname, GetTestResultSteps(projectName, testName, resultName, bname));
                })
                .OrderBy(d => d.Key);
        }


        public IEnumerable<TestStepResult> GetTestResultSteps(string projectName, string testName, string executionName, string forBroserName, bool quick = true)
        {
            var dirName = GetPathName(projectName, testName, executionName);
            var fileName = Path.Combine(dirName, GetMainFileName(forBroserName));
            var logFileName = Path.Combine(dirName, GetLogFileName(forBroserName));
            var logResults = new List<TestStepResult>();
            GetTestResultStepsLog(logFileName, "=====test step:", "=====end for: {0} =====", (s) =>
            {
                var lines = s.Split(new string[] { "\r\n" }, StringSplitOptions.None);
                var i = 0;
                while (i < lines.Length)
                    if (lines[i].StartsWith("#####"))
                    {
                        var ri = Convert(lines[i].Remove(0, 5));
                        if (ri != null && ri.Browser.Equals(forBroserName))
                        {
                            logResults.Add(ri);
                            while (++i < lines.Length && !lines[i].StartsWith("#####"))
                                ri.ExecutionLog.AppendLine(lines[i]);
                        }
                    }
                    else
                        i++;
            });
            return GetTestResultSteps(fileName, false, (tsr) =>
            {
                if (!quick) EnsureChidren(tsr,projectName,testName,executionName,forBroserName);
                var log = logResults.Where(lr => lr.StepInfo.Id.Equals(tsr.StepInfo.Id)).FirstOrDefault();
                if (log != null)
                    tsr.ExecutionLog.Append(log.ExecutionLog.ToString());
                tsr.ScreenshotFile = Path.Combine(GetPathName(projectName, testName, executionName), string.Format("screenshot.{0}.{2}.png", tsr.Browser, tsr.StepInfo.Id, tsr.StepInfo.OrderNr));
                //if (File.Exists(tsr.ScreenshotFile)){
                //    Image img =null; 
                //    try
                //    {
                //        img = Image.FromFile(tsr.ScreenshotFile);
                //        tsr.Screenshot = (Image)img.Clone();
                //        img.Dispose();
                //        img=null;
                //    }
                //    catch (Exception ex)
                //    {
                //        DataContext.Log.Error("can't read screenshot file form:" + tsr.ScreenshotFile, ex);
                //    }
                //    if(img!=null) 
                //    {
                //        img.Dispose();
                //        img=null;
                //    }
                //}
                return tsr;
                
            });

        }

        private void EnsureChidren(TestStepResult tStepResult, string projectName, string testName, string executionName, string forBroserName)
        {
            var stepDirName = Path.Combine(GetPathName(projectName, testName, executionName), tStepResult.StepInfo.OrderNr.ToString());
            if (!Directory.Exists(stepDirName)) return;
            tStepResult.Children=GetTestResultSteps(projectName, testName, Path.Combine(executionName,tStepResult.StepInfo.OrderNr.ToString()), forBroserName,false);
            foreach (var tsr in tStepResult.Children)
                tsr.Parent = tStepResult;
        }

        private void GetTestResultStepsLog(string logFileName, string startPrefix, string stopFormat, Action<string> onProcessStepLog)
        {
            if (File.Exists(logFileName))
                using (var fs = File.OpenRead(logFileName))
                using (var sr = new StreamReader(fs))
                    while (!sr.EndOfStream)
                    {
                        var s = sr.ReadLine();
                        if (s.StartsWith(startPrefix))
                        {
                            var id = s.Remove(0, startPrefix.Length).RemoveLast(5).Trim();
                            var sb = new StringBuilder();
                            var stopstring = string.Format(stopFormat, id);
                            while (!sr.EndOfStream)
                            {
                                s = sr.ReadLine();
                                if (!s.StartsWith(stopstring))
                                    sb.AppendLine(s);
                                else
                                {
                                    onProcessStepLog(sb.ToString());
                                    break;
                                }
                            }

                        }
                    }
        }



        private IEnumerable<TestStepResult> GetTestResultSteps(string filename, bool quick = false, Func<TestStepResult, TestStepResult> beforeReturn = null)
        {
            if (File.Exists(filename))
                using (var fs = File.OpenRead(filename))
                using (var sr = new StreamReader(fs))
                    while (!sr.EndOfStream)
                    {
                        var ritem = Convert(sr.ReadLine(), quick);
                        if (ritem != null)
                        {
                            if (beforeReturn == null) yield return ritem;
                            else yield return beforeReturn(ritem);
                        }
                    }
        }

        #region Save /restore information

        private object lockobj = new object();
        public TestStepResult SaveResult(string projectName, string testName, string executionName, TestStepResult data)
        {
            projectName = !Path.IsPathRooted(projectName) ? projectName : Path.GetFileName(projectName.TrimEnd('\\'));
            testName = !Path.IsPathRooted(testName) ? testName : Path.GetFileName(testName);
            executionName = GetFinalExecutionPath(data, executionName);
            lock (lockobj)
            {
                SaveMainResult(projectName, testName, executionName, data);
                SaveExtendedResult(projectName, testName, executionName, data);
            }
            return data;
        }

        private string GetFinalExecutionPath(TestStepResult data, string executionName)
        {
            if (data.Parent == null) return executionName;
            var p = data;
            var s = "";
            while (p.Parent != null)
            {
                s = Path.Combine(p.Parent.StepInfo.OrderNr.ToString(), s);
                p = p.Parent;
            }
            return Path.Combine(executionName, s);
        }

        private void SaveExtendedResult(string projectName, string testName, string executionName, TestStepResult data)
        {        
            if (data.Screenshot != null)
            {
                data.ScreenshotFile = Path.Combine(GetPathName(projectName, testName, executionName), string.Format("screenshot.{0}.{2}.png", data.Browser, data.StepInfo.Id, data.StepInfo.OrderNr));
                //data.ExecutionLog.AppendFormat("Saving screen shot:" + data.ScreenshotFile);

                data.Screenshot.Save(data.ScreenshotFile);
            }
            var file = Path.Combine(GetPathName(projectName, testName, executionName), GetLogFileName(data.Browser));
            UpdateExFile(file,data.StepInfo==null?-1:data.StepInfo.OrderNr, () =>
            {
                return data.StepInfo==null?null:string.Format("#####{0}\r\n\r\n{1}\r\n", Convert(data), data.ExecutionLog);
            });
        }
        private void SaveMainResult(string projectName, string testName, string executionName, TestStepResult data)
        {            
            var file = Path.Combine(GetPathName(projectName, testName, executionName), GetMainFileName(data.Browser));
            UpdateMainFile(file,data.StepInfo==null?-1:data.StepInfo.OrderNr, () =>
            {
                return data.StepInfo == null ? null : Convert(data);
            });
        }

        private string GetMainFileName(string browserName)
        {
            return string.Concat(mainPrefix, browserName, mainSuffix);
        }

        private string GetLogFileName(string browserName)
        {
            return string.Concat(logPrefix, browserName, logSuffix);
        }

        private void UpdateMainFile<T>(string fileName, T recordId, Func<string> getDataToRecord)
        {
            var sb = new StringBuilder();
            var linePrefix = recordId.ToString();
            var beenFound = false;
            var data = getDataToRecord();
            if (File.Exists(fileName))
                using (var fs = File.OpenRead(fileName))
                using (var sr = new StreamReader(fs))
                    while (!sr.EndOfStream)
                    {
                        var s = sr.ReadLine();
                        if (s.StartsWith(linePrefix))
                        {
                            beenFound = true;
                            s = data;
                        }
                        sb.AppendLine(s);
                    }
            if (!beenFound && !string.IsNullOrEmpty(data)) sb.AppendLine(data);
            File.WriteAllText(fileName, sb.ToString());
        }
        private void UpdateExFile<T>(string fileName, T recordId, Func<string> getDataToRecord)
        {
            var sb = new StringBuilder();
            var lineStartPrefix = string.Format("=====test step: {0} =====", recordId);
            var lineEndPrefix = string.Format("=====end for: {0} =====", recordId);
            var beenFound = false;
            var data = getDataToRecord();
            if (File.Exists(fileName))
                using (var fs = File.OpenRead(fileName))
                using (var sr = new StreamReader(fs))
                    while (!sr.EndOfStream)
                    {
                        var s = sr.ReadLine();
                        if (s.StartsWith(lineStartPrefix))
                        {
                            beenFound = true;
                            s = data;
                        }
                        sb.AppendLine(s);
                    }
            if (!beenFound && !string.IsNullOrEmpty(data))
            {
                sb.AppendFormat("=====test step: {0} =====\r\n", recordId);
                sb.AppendLine(data);
                sb.AppendFormat("=====end for: {0} =====\r\n", recordId);
            }
            File.WriteAllText(fileName, sb.ToString());
        }
        #endregion


        public string Convert(TestStepResult data)
        {
            var sb = new StringBuilder();
            sb.AppendFormat("{0}\t\t{1}\t\t{2}\t\t{3}\t\t{4}", data.Result, data.StepInfo.OrderNr, data.Browser, DataContext.TestStepManager.Convert(data.StepInfo), data.StepInfo.Id);
            return sb.ToString();
        }


        public TestStepResult Convert(string data, bool quick = false)
        {
            TestStep stepInfo = null;
            ExecutionResult r = ExecutionResult.NoResult;
            string browser = "empty";
            try
            {
                var ss = (data ?? "").Split(new string[] { "\t\t" }, StringSplitOptions.RemoveEmptyEntries);

                if (ss.Length < 1 || Enum.TryParse<ExecutionResult>(ss[0], out r))
                    r = ExecutionResult.NoResult;
                int stepNr;
                if (!(ss.Length > 0 && int.TryParse(ss[1], out stepNr))) stepNr = -1;
                browser = ss.Length < 2 ? "empty" : ss[2];
                stepInfo = quick || ss.Length < 3 ? null : DataContext.TestStepManager.Convert(ss[3]);
                Guid stepId;
                if (ss.Length < 4 || !Guid.TryParse(ss[4], out stepId)) stepId = Guid.Empty;
                if (stepInfo == null) stepInfo = new TestStep();
                stepInfo.Id = stepId;
                stepInfo.OrderNr = stepNr;
            }
            catch (Exception ex)
            {
                DataContext.Log.Fatal("Can't transform text to step result:" + data, ex);
            }
            var result = new TestStepResult(stepInfo) { Result = r, Browser = browser };
            return result;
        }


        private string GetPathName(string projectName = null, string testName = null, string sessionName = null)
        {
            var result = EnsureDirectory(ResultsRootPath,"");
            if (projectName != null) result = EnsureDirectory(result, projectName);
            if (testName != null) result = EnsureDirectory(result, testName);
            if (sessionName != null) result = EnsureDirectory(result, sessionName);
            return result;
        }
        private string EnsureDirectory(string parent, string name)
        {
            var result = Path.Combine(parent, name);
            if (Directory.Exists(result)) return result;
            Directory.CreateDirectory(result);
            return result;
        }
    }


    static class stringEx
    {
        public static string RemoveLast(this string data, int length)
        {
            if (data == null) return data;
            return data.Substring(0, data.Length - length);
        }
    }
}
